/*
 *  Copyright 2020 Huawei Technologies Co., Ltd.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.edgegallery.mecm.apm.service;

import static org.edgegallery.mecm.apm.utils.ApmServiceHelper.getProtocol;
import static org.edgegallery.mecm.apm.utils.ApmServiceHelper.isSuffixExist;
import static org.edgegallery.mecm.apm.utils.ApmServiceHelper.saveInputStreamToFile;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.InputStream;
import java.nio.file.InvalidPathException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.io.FileUtils;
import org.edgegallery.mecm.apm.apihandler.ApmSyncHandler;
import org.edgegallery.mecm.apm.exception.ApmException;
import org.edgegallery.mecm.apm.model.AppPackageInfo;
import org.edgegallery.mecm.apm.model.AppPackageSyncInfo;
import org.edgegallery.mecm.apm.model.AppRepo;
import org.edgegallery.mecm.apm.model.AppStore;
import org.edgegallery.mecm.apm.model.AppTemplate;
import org.edgegallery.mecm.apm.model.Intent;
import org.edgegallery.mecm.apm.model.PkgSyncInfo;
import org.edgegallery.mecm.apm.model.SwImageDescr;
import org.edgegallery.mecm.apm.model.dto.AppPackageDto;
import org.edgegallery.mecm.apm.model.dto.AppPackageInfoDto;
import org.edgegallery.mecm.apm.model.dto.AppTemplateDto;
import org.edgegallery.mecm.apm.model.dto.AppTemplateInputAttrDto;
import org.edgegallery.mecm.apm.model.dto.MecHostDto;
import org.edgegallery.mecm.apm.model.dto.templatedto.Cpu;
import org.edgegallery.mecm.apm.model.dto.templatedto.Disk;
import org.edgegallery.mecm.apm.model.dto.templatedto.EdgeResourceInfo;
import org.edgegallery.mecm.apm.model.dto.templatedto.Mem;
import org.edgegallery.mecm.apm.model.dto.templatedto.Resource;
import org.edgegallery.mecm.apm.model.dto.templatedto.ResourceInfo;
import org.edgegallery.mecm.apm.utils.ApmServiceHelper;
import org.edgegallery.mecm.apm.utils.ApmV2Response;
import org.edgegallery.mecm.apm.utils.CompressUtility;
import org.edgegallery.mecm.apm.utils.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

@Getter
@Setter
@Service("ApmServiceFacade")
public class ApmServiceFacade {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApmServiceFacade.class);
    private static final String TAR_GZ = ".tar.gz";
    private static final String LCMCONTROLLER_URL = "/lcmcontroller/v2/tenants/";
    private static final String PACKAGES_URL = "/packages/";
    private static final String HTTPS = "https://";
    private static final String PATH_DELIMITER = "/";
    private static final String CSAR = ".csar";

    @Autowired
    private ApmService apmService;

    @Autowired
    private ApmSyncHandler apmSyncHandler;

    @Autowired
    private DbService dbService;

    @Autowired
    private RestServiceImpl restService;

    @Value("${apm.inventory-endpoint}")
    private String inventoryService;

    @Value("${apm.inventory-port}")
    private String inventoryServicePort;

    @Value("${apm.package-dir:/usr/app/packages}")
    private String localDirPath;

    private String localPackagePath;

    @Autowired
    private RestTemplate restTemplate;

    @Value("${apm.mecm-repo-endpoint:}")
    private String mecmRepoEndpoint;

    @Value("${apm.push-image:}")
    private String uploadDockerImage;

    @Value("${server.ssl.enabled:false}")
    private String isSslEnabled;

    enum IntentEnum {
        MATCH_ALL,
        MATCH_BOTH,
        MATCH_ANY;
    }

    /**
     * Updates Db and distributes docker application image to host.
     *
     * @param accessToken   access token
     * @param tenantId      tenant ID
     * @param appPackageDto appPackage details
     * @param syncAppPkg    app package sync info
     */
    @Async
    public void onboardApplication(String accessToken, String tenantId, AppPackageDto appPackageDto,
                                   PkgSyncInfo syncAppPkg) {
        String packageId = appPackageDto.getAppPkgId();
        List<SwImageDescr> imageInfoList;

        try {
            InputStream stream = apmService.downloadAppPackage(appPackageDto.getAppPkgPath(), packageId, accessToken);
            String localFilePath = saveInputStreamToFile(stream, packageId, tenantId, localDirPath);

            imageInfoList = apmService.getAppImageInfo(tenantId, localFilePath, appPackageDto.getAppPkgId());
            String appDeployType = apmService.getAppPackageDeploymentType(tenantId, appPackageDto.getAppPkgId());

            AppTemplate appTemplate = apmService.getApplicationTemplateInfo(appPackageDto, tenantId, appDeployType);
            dbService.createOrUpdateAppTemplate(tenantId, appTemplate);

            if ("container".equalsIgnoreCase(appDeployType)) {
                onboardContainerBasedAppPkg(accessToken, tenantId, appPackageDto, syncAppPkg, imageInfoList);
                addAppSyncInfoDb(appPackageDto, syncAppPkg, Constants.SUCCESS);
            } else if ("vm".equalsIgnoreCase(appDeployType)) {
                onboardVmBasedAppPkg(accessToken, tenantId, appPackageDto);
                addAppSyncInfoDb(appPackageDto, syncAppPkg, Constants.SUCCESS);
            }
        } catch (ApmException ex) {
            LOGGER.error(Constants.DISTRIBUTION_FAILED, ex.getMessage());
            return;
        }
        LOGGER.info("On-boading completed...");
    }

    /**
     * Distributes docker application image to host.
     *
     * @param accessToken   access token
     * @param tenantId      tenant ID
     * @param appPackageDto appPackage details
     * @param localFilePath local package path
     * @param syncAppPkg    sync application package
     */
    @Async
    public void onboardApplication(String accessToken, String tenantId, AppPackageDto appPackageDto,
                                   String localFilePath, PkgSyncInfo syncAppPkg) {

        try {
            List<SwImageDescr> imageInfoList = apmService.getAppImageInfo(tenantId, localFilePath,
                                                                          appPackageDto.getAppPkgId());
            String appDeployType = apmService.getAppPackageDeploymentType(tenantId, appPackageDto.getAppPkgId());

            AppTemplate appTemplate = apmService.getApplicationTemplateInfo(appPackageDto, tenantId, appDeployType);
            dbService.createOrUpdateAppTemplate(tenantId, appTemplate);

            if ("container".equalsIgnoreCase(appDeployType)) {
                onboardContainerBasedAppPkg(accessToken, tenantId, appPackageDto, syncAppPkg, imageInfoList);
            } else if ("vm".equalsIgnoreCase(appDeployType)) {
                onboardVmBasedAppPkg(accessToken, tenantId, appPackageDto);
            }
        } catch (ApmException | IllegalArgumentException ex) {
            LOGGER.error(Constants.DISTRIBUTION_FAILED, ex.getMessage());
            return;
        }
        LOGGER.info("On-boading completed...");
    }

    private boolean isDockerImageAvailableInPkg(String dockerImage) {
        return isSuffixExist(dockerImage, ".tar") || isSuffixExist(dockerImage, TAR_GZ)
                || isSuffixExist(dockerImage, ".tgz");
    }

    private void onboardContainerBasedAppPkg(String accessToken, String tenantId, AppPackageDto appPackageDto,
                                             PkgSyncInfo syncAppPkg, List<SwImageDescr> imageInfoList) {
        String packageId = appPackageDto.getAppPkgId();
        String dockerImgspath;
        boolean downloadImg = true;
        Set<String> loadedImgs = new HashSet<>();
        try {
            for (SwImageDescr imageDescr : imageInfoList) {
                if (isDockerImageAvailableInPkg(imageDescr.getSwImage())) {
                    downloadImg = false;

                    LOGGER.info("application package contains docker images...");
                    dockerImgspath = apmService.unzipDockerImages(appPackageDto.getAppPkgId(), tenantId);
                    apmService.loadDockerImages(packageId, imageInfoList, loadedImgs);

                    FileUtils.deleteQuietly(new File(dockerImgspath + ".zip"));
                    FileUtils.deleteQuietly(new File(dockerImgspath));

                    break;
                }
            }

            syncDockerImagesFromSrcToMecmRepo(appPackageDto, imageInfoList, syncAppPkg, downloadImg, accessToken);

            apmService.updateAppPackageWithRepoInfo(tenantId, packageId);

            String sourceDir = apmService.getLocalIntendedDir(packageId, tenantId);
            CompressUtility.compressAppPackage(sourceDir, sourceDir + File.separator + packageId + CSAR);
        } catch (ApmException | IllegalArgumentException ex) {
            LOGGER.error(Constants.DISTRIBUTION_FAILED, ex.getMessage());
            apmService.deleteAppPkgDockerImages(loadedImgs);
            dbService.updateDistributionStatusOfAllHost(tenantId, packageId, Constants.ERROR, ex.getMessage());
            throw new ApmException(ex.getMessage());
        }

        distributeApplication(tenantId, appPackageDto, accessToken);
    }

    private void onboardVmBasedAppPkg(String accessToken, String tenantId, AppPackageDto appPackageDto) {

        String sourceDir = apmService.getLocalIntendedDir(appPackageDto.getAppPkgId(), tenantId);
        CompressUtility.compressAppPackage(sourceDir, sourceDir + File.separator + appPackageDto.getAppPkgId() + CSAR);

        distributeApplication(tenantId, appPackageDto, accessToken);

        LOGGER.info("On-boading vm based applicaiton package completed...");
    }

    private void addAppSyncInfoDb(AppPackageDto appPackageDto, PkgSyncInfo syncInfo, String operationalInfo) {

        //Check if app package sync info exist in DB.
        if (!dbService.isAppPackageSyncInfoExistInDb(appPackageDto.getAppPkgId())) {
            AppPackageInfo pkgInfo = new AppPackageInfo();
            pkgInfo.setAppPkgInfoId(appPackageDto.getAppPkgId());
            pkgInfo.setAppId(appPackageDto.getAppId());
            pkgInfo.setPackageId(syncInfo.getPackageId());
            pkgInfo.setName(appPackageDto.getAppPkgName());
            pkgInfo.setSyncStatus(Constants.APP_IN_SYNC);
            pkgInfo.setAppstoreIp(syncInfo.getAppstoreIp());
            pkgInfo.setOperationalInfo(operationalInfo);
            pkgInfo.setShortDesc(appPackageDto.getAppPkgDesc());
            pkgInfo.setProvider(appPackageDto.getAppProvider());
            pkgInfo.setAffinity(appPackageDto.getAppPkgAffinity());
            pkgInfo.setVersion(appPackageDto.getAppPkgVersion());
            pkgInfo.setAppstoreEndpoint(syncInfo.getAppstoreIp() + ":" + syncInfo.getAppstorePort());
            dbService.addAppSyncPackageInfoDB(pkgInfo);
        }
    }

    /**
     * Returns app package info.
     *
     * @param tenantId     tenant ID
     * @param appPackageId app package ID
     * @return app package info
     */
    public AppPackageDto getAppPackageInfo(String tenantId, String appPackageId) {
        return dbService.getAppPackageWithHost(tenantId, appPackageId);
    }

    /**
     * Returns app template info.
     *
     * @param tenantId     tenant ID
     * @param appPackageId app package ID
     * @return app package info
     */
    public AppTemplate getAppTemplateInfo(String tenantId, String appPackageId) {
        return dbService.getApplicationTemplate(tenantId, appPackageId);
    }

    /**
     * Deletes app package.
     *
     * @param tenantId     tenant ID
     * @param appPackageId app package ID
     * @return hosts on which app package to be deleted
     */
    public List<String> deleteAppPackage(String tenantId, String appPackageId) {
        dbService.deleteAppPackage(tenantId, appPackageId);
        List<String> hosts = dbService.deleteHost(tenantId, appPackageId);
        apmService.deleteAppPackageFile(ApmServiceHelper.getPackageDirPath(localDirPath, appPackageId, tenantId));
        return hosts;
    }

    /**
     * Deletes distributed application package on host.
     *
     * @param tenantId    tenant ID
     * @param hostIp      host ip
     * @param packageId   package ID
     * @param accessToken access token
     * @throws ApmException exception if failed to get edge repository details
     */
    public void deleteDistributedAppPackageOnHost(String tenantId, String hostIp,
                                                  String packageId, String accessToken) {
        try {
            String mepmEndPoint = apmService.getMepmCfgOfHost(tenantId, hostIp, accessToken);

            String url = new StringBuilder(getProtocol(isSslEnabled)).append(mepmEndPoint)
                    .append(LCMCONTROLLER_URL).append(tenantId)
                    .append(PACKAGES_URL).append(packageId).append("/hosts/").append(hostIp).toString();

            apmService.sendDeleteRequest(url, accessToken);
        } catch (NoSuchElementException | ApmException ex) {
            LOGGER.error("failed to delete package on host {}", hostIp);
        }
    }

    /**
     * Deletes distributed application package.
     *
     * @param tenantId    tenant ID
     * @param hostIp      host ip
     * @param packageId   package ID
     * @param accessToken access token
     * @throws ApmException exception if failed to get edge repository details
     */
    public void deleteDistributedAppPackage(String tenantId, String hostIp, String packageId, String accessToken) {
        try {
            String mepmEndPoint = apmService.getMepmCfgOfHost(tenantId, hostIp, accessToken);

            String url = new StringBuilder(getProtocol(isSslEnabled)).append(mepmEndPoint)
                    .append(LCMCONTROLLER_URL).append(tenantId)
                    .append(PACKAGES_URL).append(packageId).toString();

            apmService.sendDeleteRequest(url, accessToken);
        } catch (NoSuchElementException | ApmException ex) {
            LOGGER.error("failed to delete package on host {}", hostIp);
        }
    }

    /**
     * Returns list of app package info.
     *
     * @param tenantId tenant ID
     * @return list of app package info
     */
    public List<AppPackageDto> getAllAppPackageInfo(String tenantId) {
        return dbService.getAllAppPackage(tenantId);
    }

    /**
     * Deletes app package in host.
     *
     * @param tenantId     tenant ID
     * @param appPackageId app package ID
     * @param hostIp       host Ip
     */
    public void deleteAppPackageInHost(String tenantId, String appPackageId,
                                       String hostIp) {
        dbService.deleteHostWithIp(tenantId, appPackageId, hostIp);
    }

    /**
     * Returns app package csar file.
     *
     * @param tenantId  tenant ID
     * @param packageId package ID
     * @return app package csar file
     */
    public InputStream getAppPackageFile(String tenantId, String packageId) {
        return apmService.getAppPackageFile(ApmServiceHelper.getLocalFilePath(localDirPath, packageId, tenantId));
    }

    /**
     * Create app package record in db.
     *
     * @param tenantId      tenant ID
     * @param appPackageDto app package dto
     */
    public void createAppPackageEntryInDb(String tenantId, AppPackageDto appPackageDto) {
        dbService.createAppPackage(tenantId, appPackageDto);
        dbService.createHost(tenantId, appPackageDto);
    }

    private void syncDockerImagesFromSrcToMecmRepo(AppPackageDto appPackageDto,
                                                   List<SwImageDescr> imageInfoList, PkgSyncInfo syncAppPkg,
                                                   boolean downloadImage, String accessToken) {
        String packageId = appPackageDto.getAppPkgId();
        Set<String> downloadedImgs = null;
        Set<String> uploadedImgs = null;
        boolean imagesInSync = false;

        try {
            AppPackageInfo appPkinfoDb = dbService.getAppPackageSyncInfo(packageId);
            if (Constants.APP_IN_SYNC.equals(appPkinfoDb.getSyncStatus())) {
                imagesInSync = true;
            }
        } catch (NoSuchElementException ex) {
            LOGGER.error("Image is not in sync");
        }

        try {
            if (!imagesInSync && Boolean.parseBoolean(uploadDockerImage)) {
                LOGGER.info("application package not in sync, download images and upload to mecm repo");

                imageInfoList = getImagesExcludingAlreadyUploaded(imageInfoList, accessToken);

                //Download docker images if path in swImagewDescr refers to remote
                if (downloadImage) {
                    downloadedImgs = new HashSet<>();
                    apmService.downloadAppImage(syncAppPkg, imageInfoList, downloadedImgs);
                }

                uploadedImgs = new HashSet<>();
                apmService.uploadAppImage(imageInfoList, uploadedImgs);
            }
        } catch (ApmException e) {
            throw new ApmException(e.getMessage());
        } finally {
            apmService.deleteAppPkgDockerImages(downloadedImgs);
            apmService.deleteAppPkgDockerImages(uploadedImgs);
        }
    }

    private void distributeApplication(String tenantId, AppPackageDto appPackageDto, String accessToken) {
        String packageId = appPackageDto.getAppPkgId();
        for (MecHostDto host : appPackageDto.getMecHostInfo()) {
            String distributionStatus;
            String error = Constants.EMPTY_STRING;
            try {
                LOGGER.info("Entering distribution flow");
                uploadAndDistributeApplicationPackage(accessToken, host.getHostIp(), tenantId,
                        appPackageDto.getAppId(), packageId);
                //  wait for distribution status to fetch from aapplcm
                String mepmEndPoint = apmService.getMepmCfgOfHost(tenantId, host.getHostIp(), accessToken);
                updateDistributionStatus(mepmEndPoint, tenantId, packageId, accessToken, error, host.getHostIp());

            } catch (ApmException e) {
                distributionStatus = Constants.ERROR;
                error = e.getMessage();
                LOGGER.error(Constants.DISTRIBUTION_IN_HOST_FAILED, packageId, host.getHostIp());
                dbService.updateDistributionStatusOfHost(tenantId, packageId, host.getHostIp(), distributionStatus,
                        error);
                throw new ApmException(e.getMessage());
            }
        }
    }

    private void updateDistributionStatus(String mepmEndPoint, String tenantId, String packageId, String accessToken,
                                          String error, String host) {
        String status = Constants.EMPTY_STRING;
        String response = Constants.EMPTY_STRING;
        boolean timeout = false;
        JsonArray jsonarray = new JsonArray();

        for (int i = 0; i < 20; i++) {
            response = getAppPkgDistributionStatus(mepmEndPoint, tenantId,
                    packageId, accessToken);
            LOGGER.info("response is : {} attempt no. {}", response, i);
            JsonObject json = new JsonParser().parse(response).getAsJsonObject();
            JsonArray output = json.get("data").getAsJsonArray();

            for (JsonElement hosts : output) {
                jsonarray = hosts.getAsJsonObject().get("mecHostInfo").getAsJsonArray();
            }
            for (JsonElement element : jsonarray) {
                status = element.getAsJsonObject().get("status").getAsString();
            }

            if (status.equalsIgnoreCase("Distributing")
                    || status.equalsIgnoreCase("uploading")) {
                try {
                    Thread.sleep(30 * 1000L);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    LOGGER.error("InterruptedException in updateDistributionStatus");
                }
                timeout = true;
            }
            if (status.equalsIgnoreCase("Distributed")
                    || status.equalsIgnoreCase("Error")
                    || status.equalsIgnoreCase("uploaded")) {
                timeout = false;
                LOGGER.info("status is : {} attempt no. {}", status, i);
                break;
            }
            LOGGER.info("status is : {} attempt no. {}", status, i);
        }

        if (timeout) {
            status = "Timeout";
        }
        dbService.updateDistributionStatusOfHost(tenantId, packageId, host,
                status, error);
        LOGGER.info("Application package {}, on-boarding on {} completed...", packageId, host);
    }

    private String getAppPkgDistributionStatus(String mepmEndPoint, String tenantId,
                                               String pkgId, String accessToken) {
        LOGGER.info("distribute application package ");
        String url = new StringBuilder(getProtocol(isSslEnabled)).append(mepmEndPoint)
                .append(LCMCONTROLLER_URL).append(tenantId)
                .append(PACKAGES_URL).append(pkgId).toString();
        return apmService.sendGetRequest(url, accessToken);
    }

    /**
     * Returns manifest for a given docker image reference.
     *
     * @param repo        repository endpoint
     * @param repository  docker repository
     * @param tag         docker image tag
     * @param accessToken access token
     * @return returns appstore configuration info
     * @throws ApmException exception if failed to get appstore configuration details
     */
    public String checkIfManifestPresentRepo(String repo, String repository, String tag, String accessToken) {
        String url;
        String[] repos = repo.split(":");

        if (repos.length == 1) {
            url = new StringBuilder(getProtocol(isSslEnabled)).append(repo).append(":")
                    .append("443").append("/v2/")
                    .append(repository)
                    .append("/manifests/")
                    .append(tag).toString();
        } else {
            url = new StringBuilder(getProtocol(isSslEnabled)).append(repo).append("/v2/")
                    .append(repository)
                    .append("/manifests/")
                    .append(tag).toString();
        }
        LOGGER.info("check if manifest available in repo: {}", url);

        String response = apmService.sendGetRequest(url, accessToken);
        LOGGER.info("manifest query result {}", response);
        return response;
    }

    private List<SwImageDescr> getImagesExcludingAlreadyUploaded(List<SwImageDescr> imageInfoList,
                                                                 String accessToken) {

        List<SwImageDescr> imagesLstExcImgsInRepo = new LinkedList<>();
        if (imageInfoList == null) {
            LOGGER.error("swImageDescr image info list is null");
            throw new ApmException("swImageDescr image info list is null");
        }

        for (SwImageDescr imageInfo : imageInfoList) {
            try {
                String tag = imageInfo.getVersion();
                String name = imageInfo.getName();
                if (tag == null || name == null) {
                    LOGGER.error("could not find image name or image version in descriptor");
                    throw new ApmException("could not find image name or image version in descriptor");
                }
                String[] imageName = name.split(":");
                String mecmRepository = "/mecm/" + imageName[0];

                checkIfManifestPresentRepo(mecmRepoEndpoint, mecmRepository, tag, accessToken);

                LOGGER.info("image is available in repo, skip download/upload {}", imageInfo.getSwImage());
            } catch (ApmException | NoSuchElementException ex) {
                imagesLstExcImgsInRepo.add(imageInfo);
                LOGGER.error("image is not available in repo, download image: {}", imageInfo.getSwImage());
            }
        }
        return imagesLstExcImgsInRepo;
    }

    /**
     * Upload and distribute application package on the edge host.
     *
     * @param accessToken access token
     * @param hostIp      host IP
     * @param tenantId    tenant ID
     * @param appId       add ID
     * @param packageId   package ID
     */
    @Async
    public void uploadAndDistributeApplicationPackage(String accessToken, String hostIp, String tenantId,
                                                      String appId, String packageId) {
        try {
            String mepmEndPoint = apmService.getMepmCfgOfHost(tenantId, hostIp, accessToken);

            uploadApplicationPackage(mepmEndPoint, tenantId, appId, packageId, accessToken);

            distributeApplicationPackage(mepmEndPoint, tenantId, packageId, hostIp, accessToken);
        } catch (ApmException | NoSuchElementException ex) {
            LOGGER.error("failed to upload and distribute application package {} on host {}", packageId, hostIp);
            throw new ApmException("failed to upload and distribute application");
        }
    }

    private void distributeApplicationPackage(String mepmEndPoint, String tenantId,
                                              String pkgId, String hostIp, String accessToken) {
        LOGGER.info("distribute application package");
        String url = new StringBuilder(getProtocol(isSslEnabled)).append(mepmEndPoint)
                .append(LCMCONTROLLER_URL).append(tenantId)
                .append(PACKAGES_URL).append(pkgId).toString();

        List<String> hosts = new LinkedList<>();
        hosts.add(hostIp);
        Map<String, List<String>> hostsMap = new HashMap<>();
        hostsMap.put("hostIp", hosts);
        apmService.sendPostRequest(url, new Gson().toJson(hostsMap).toString(), accessToken);
    }

    private void uploadApplicationPackage(String mepmEndPoint, String tenantId,
                                          String appId, String pkgId, String accessToken) {
        LOGGER.info("upload application package");
        String url = new StringBuilder(getProtocol(isSslEnabled)).append(mepmEndPoint)
                .append(LCMCONTROLLER_URL).append(tenantId).append("/packages").toString();
        try {
            String packagePath = new StringBuilder(localDirPath).append(File.separator).append(pkgId)
                    .append(tenantId).append(PATH_DELIMITER).append(pkgId).append(CSAR).toString();
            FileSystemResource appPkgRes = new FileSystemResource(new File(packagePath));

            // Preparing request parts.
            LinkedMultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
            parts.add("package", appPkgRes);
            parts.add("packageId", pkgId);
            parts.add("appId", appId);

            sendRequestWithMultipartFormData(url, parts, accessToken);
        } catch (InvalidPathException e) {
            LOGGER.error("package ID is invalid");
            throw new ApmException("invalid package path");
        } catch (ApmException e) {
            LOGGER.error("failed to upload package  {}", e.getMessage());
            throw new ApmException("upload package failed " + e.getMessage());
        }
    }

    /**
     * Send request to remote entity.
     *
     * @param url         request url
     * @param data        multipart request details
     * @param accessToken access token
     */
    private void sendRequestWithMultipartFormData(String url, LinkedMultiValueMap<String, Object> data,
                                                  String accessToken) {

        HttpHeaders headers = new HttpHeaders();
        headers.set("access_token", accessToken);
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        HttpEntity<LinkedMultiValueMap<String, Object>> entity = new HttpEntity<>(data, headers);
        try {
            LOGGER.info("upload app package {}", url);

            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);

            if (!HttpStatus.OK.equals(response.getStatusCode())) {
                LOGGER.error("upload failed, return code {}", response.getStatusCode());
                throw new ApmException("returned error from remote entity, error code {}" + response.getStatusCode());
            }
        } catch (ResourceAccessException ex) {
            LOGGER.error("upload failed, resource exception {}", ex.getMessage());
            throw new ApmException("Resource access exception" + ex.getMessage());
        } catch (HttpServerErrorException | HttpClientErrorException ex) {
            LOGGER.error("upload failed, http error exception {}", ex.getMessage());
            throw new ApmException("failed to connect" + ex.getMessage());
        }
        LOGGER.info("application package uploaded successfully");
    }

    /**
     * Returns application store configuration.
     *
     * @param appstoreIp  appstore IP
     * @param accessToken access token
     * @return app store configuration
     */
    public AppStore getAppstoreConfig(String appstoreIp, String accessToken) {

        return apmService.getAppStoreCfgFromInventory(appstoreIp, accessToken);
    }

    /**
     * Returns application repo configuration.
     *
     * @param accessToken access token
     * @return app store configuration
     */
    public List<AppRepo> getAllAppRepoConfig(String accessToken) {

        return apmService.getAllAppRepoCfgFromInventory(accessToken);
    }

    /**
     * Returns application package info.
     *
     * @param appstoreEndPoint appstore end point
     * @param accessToken      access token
     * @return app store configuration
     */
    public List<AppPackageInfoDto> getAppPackagesInfo(String appstoreEndPoint, String accessToken) {

        return apmService.getAppPackagesInfoFromAppStore(appstoreEndPoint, accessToken);
    }

    /**
     * Retrieve app package info.
     *
     * @param appstoreEndPoint app store end point
     * @param appId            app ID
     * @param packageId        app package ID
     * @param accessToken      access token
     * @return app package info
     */
    public AppPackageInfoDto getAppPackageInfoFromAppStore(String appstoreEndPoint, String appId,
                                                           String packageId, String accessToken) {
        return apmService.getAppPkgInfoFromAppStore(appstoreEndPoint, appId, packageId, accessToken);
    }

    /**
     * Adds application package info.
     *
     * @param appstoreIp appstore end point
     * @param apps       application package infos
     */
    public void deleteNonExistingPackages(String appstoreIp, List<AppPackageInfoDto> apps) {
        dbService.deleteNonExistingPackages(appstoreIp, apps);
    }

    /**
     * Adds application package info.
     *
     * @param appstoreIp   appstore IP
     * @param appstorePort appstore port
     * @param appPkgInfo   application package info
     */
    public void addAppSyncPackageInfoDB(String appstoreIp, String appstorePort, AppPackageInfoDto appPkgInfo) {
        dbService.addAppSyncPackageInfoDB(appstoreIp, appstorePort, appPkgInfo);
    }

    /**
     * Retrieves all application package info.
     *
     * @return list of application package info
     */
    public List<AppPackageInfo> getAppPackageInfoDB() {
        return dbService.getAppPackageSyncInfo();
    }

    /**
     * Retrieve application package info.
     *
     * @param id ID
     * @return application package info
     */
    public AppPackageInfo getAppPackageInfoDB(String id) {
        return dbService.getAppPackageSyncInfo(id);
    }

    /**
     * Returns true if package info exist in DB.
     *
     * @param id ID
     * @return true if available, otherwise false
     */
    public boolean isAppPackageInfoExistInDB(String id) {
        return dbService.isAppPackageSyncInfoExistInDb(id);
    }

    /**
     * Updates Db and distributes docker application image to host.
     *
     * @param accessToken access token
     * @param syncInfos   sync appPackage details
     */
    @Async
    public void syncApplicationPackages(String accessToken, AppPackageSyncInfo syncInfos) {
        List<PkgSyncInfo> pkgInfos = syncInfos.getSyncInfo();
        for (PkgSyncInfo syncInfo : pkgInfos) {
            syncInfo.setRepoInfo(syncInfos.getRepoInfo());
            syncAppPkgFromAppstoreToMecmRepo(accessToken, syncInfo);
        }
    }

    private void syncAppPkgFromAppstoreToMecmRepo(String accessToken, PkgSyncInfo syncInfo) {

        String host = syncInfo.getAppstoreIp() + ":" + syncInfo.getAppstorePort();
        String appPackageId = syncInfo.getAppId() + syncInfo.getPackageId();

        String appPkgPath = new StringBuilder(getProtocol(isSslEnabled)).append(host).append("/mec/appstore/v1/apps/")
                .append(syncInfo.getAppId()).append(PACKAGES_URL)
                .append(syncInfo.getPackageId()).append("/action/download").toString();

        Set<String> uploadedImgs = new HashSet<>();
        Set<String> downloadedImgs = new HashSet<>();
        boolean isDockerImgAvailable = false;
        List<SwImageDescr> imageInfoList;
        String dockerImgPath;
        InputStream stream;
        try {
            dbService.updateAppPackageSyncStatus(syncInfo.getAppId(), syncInfo.getPackageId(),
                    Constants.APP_SYNC_INPROGRESS, Constants.EMPTY_STRING);

            stream = apmService.downloadAppPackage(appPkgPath, syncInfo.getPackageId(), accessToken);
            String localFilePath = saveInputStreamToFile(stream, appPackageId, null, localDirPath);

            imageInfoList = apmService.getAppImageInfo(null, localFilePath, appPackageId);
            String appDeployType = apmService.getAppPackageDeploymentType(null, appPackageId);

            if ("vm".equalsIgnoreCase(appDeployType)) {
                dbService.updateAppPackageSyncStatus(syncInfo.getAppId(), syncInfo.getPackageId(),
                        Constants.APP_IN_SYNC, Constants.SUCCESS);
                return;
            }

            if (Boolean.parseBoolean(uploadDockerImage)) {
                for (SwImageDescr imageDescr : imageInfoList) {
                    if (isDockerImageAvailableInPkg(imageDescr.getSwImage())) {
                        isDockerImgAvailable = true;
                        break;
                    }
                }

                if (isDockerImgAvailable) {
                    LOGGER.info("application package contains docker images...");
                    dockerImgPath = apmService.unzipDockerImages(appPackageId, null);
                    apmService.loadDockerImages(appPackageId, imageInfoList, downloadedImgs);

                    FileUtils.deleteQuietly(new File(dockerImgPath + ".zip"));
                    FileUtils.deleteQuietly(new File(dockerImgPath));

                } else {
                    LOGGER.info("application package has image repo info to download...");
                    apmService.downloadAppImage(syncInfo, imageInfoList, downloadedImgs);
                }
                apmService.uploadAppImage(imageInfoList, uploadedImgs);
            }

            dbService.updateAppPackageSyncStatus(syncInfo.getAppId(), syncInfo.getPackageId(),
                    Constants.APP_IN_SYNC, Constants.SUCCESS);
        } catch (ApmException | IllegalArgumentException | NoSuchElementException e) {
            LOGGER.error(Constants.SYNC_APP_FAILED, appPackageId);
            dbService.updateAppPackageSyncStatus(syncInfo.getAppId(),
                    syncInfo.getPackageId(), Constants.APP_SYNC_FAILED, e.getMessage());
        } finally {
            apmService.deleteAppPkgDockerImages(downloadedImgs);
            apmService.deleteAppPkgDockerImages(uploadedImgs);
            apmService.deleteAppPackageFile(appPkgPath);
        }
    }

    /**
     * resourceInfo.
     */
    public ApmV2Response resourceInfo(String accessToken, String tenantId, String packageId,
                                               String appPkgPath) {
        LOGGER.info("inside resoruce function");

        AppTemplateDto appTemplateDto = new AppTemplateDto();

        try {
            InputStream stream = apmService.downloadAppPackage(appPkgPath, packageId, accessToken);
            String localFilePath = saveInputStreamToFile(stream, packageId, tenantId, localDirPath);
            LOGGER.info("localPath: {}", localFilePath);


            //unzip app package
            String intendedDir = apmService.getLocalIntendedDir(packageId, tenantId);
            LOGGER.info("intendeDir: {}", intendedDir);
            CompressUtility.unzipApplicationPacakge(localFilePath, intendedDir);

            ResourceInfo resourceInfo = apmService.getVduComputeInfo(tenantId, appPkgPath, packageId, appTemplateDto,
                    false);
            LOGGER.info("resourceInfo: {}", resourceInfo);


            String appDeployType = apmService.getAppPackageDeploymentType(tenantId, packageId);

            List<EdgeResourceInfo> queryKpiList = Collections.EMPTY_LIST;

            if ("container".equalsIgnoreCase(appDeployType)) {
                LOGGER.info("container based not yet there");
                throw new ApmException(Constants.MAX_LIMIT_REACHED_ERROR);
            } else if ("vm".equalsIgnoreCase(appDeployType)) {
                Map<String, String> queryKpi = apmSyncHandler.queryKpi(tenantId, accessToken, "openstack");
                queryKpiList = getResourceUsedInfo(queryKpi, resourceInfo);
            }

            return new ApmV2Response(queryKpiList, HttpStatus.OK.value(), "success");

        } catch (ApmException ex) {
            LOGGER.error(Constants.DISTRIBUTION_FAILED, ex.getMessage());
            return new ApmV2Response(null, HttpStatus.BAD_REQUEST.value(), ex.getMessage());
        }
    }

    /**
     * getResourceUsedInfo.
     */
    public static List<EdgeResourceInfo> getResourceUsedInfo(Map<String, String> queryKpi, ResourceInfo resourceInfo) {
        List<EdgeResourceInfo> edgeDetails = new LinkedList<>();
        EdgeResourceInfo edgeResourceInfo;
        Resource resource;
        Cpu cpu;
        Mem mem;
        Disk disk;

        for (Map.Entry map : queryKpi.entrySet()) {

            edgeResourceInfo = new EdgeResourceInfo();
            resource = new Resource();

            edgeResourceInfo.setEdge(map.getKey().toString());

            JsonObject jsonObject = new JsonParser().parse(map.getValue().toString()).getAsJsonObject();
            for (String keys : jsonObject.keySet()) {

                if (keys.equals(Constants.VIRTUAL_CPU_TOTAL)) {
                    int cpuRemain = jsonObject.get(Constants.VIRTUAL_CPU_TOTAL).getAsInt()
                            - jsonObject.get(Constants.VIRTUAL_CPU_USED).getAsInt();
                    if (cpuRemain > resourceInfo.getNumVirtualCpu()) {
                        cpu = new Cpu(jsonObject.get(Constants.VIRTUAL_CPU_USED).toString(), jsonObject
                                .get(Constants.VIRTUAL_CPU_TOTAL).toString(), cpuRemain,
                                resourceInfo.getNumVirtualCpu());
                    } else {
                        cpu = new Cpu(jsonObject.get(Constants.VIRTUAL_CPU_USED).toString(),
                                jsonObject.get(Constants.VIRTUAL_CPU_TOTAL).toString(), cpuRemain,
                                resourceInfo.getNumVirtualCpu());
                    }
                    resource.setCpu(cpu);
                }
                if (keys.equals("virtual_local_storage_total")) {
                    int localStorageRemain = jsonObject.get("virtual_local_storage_total").getAsInt()
                            - jsonObject.get("virtual_local_storage_used").getAsInt();
                    if (localStorageRemain > resourceInfo.getSizeOfStorage()) {
                        disk = new Disk(jsonObject.get("virtual_local_storage_used").toString(),
                                jsonObject.get("virtual_local_storage_total").toString(), localStorageRemain,
                                resourceInfo.getSizeOfStorage());
                    } else {
                        disk = new Disk(jsonObject.get("virtual_local_storage_used").toString(),
                                jsonObject.get("virtual_local_storage_total").toString(), localStorageRemain,
                                resourceInfo.getSizeOfStorage());
                    }
                    resource.setDisk(disk);
                }
                if (keys.equals(Constants.VIRTUAL_MEM_TOTAL)) {
                    int memRemain = jsonObject.get(Constants.VIRTUAL_MEM_TOTAL).getAsInt()
                            - jsonObject.get(Constants.VIRTUAL_MEM_USED).getAsInt();
                    if (memRemain > resourceInfo.getVirtualMemSize()) {
                        mem = new Mem(jsonObject.get(Constants.VIRTUAL_MEM_USED).toString(),
                                jsonObject.get(Constants.VIRTUAL_MEM_TOTAL).toString(), memRemain,
                                resourceInfo.getVirtualMemSize());
                    } else {
                        mem = new Mem(jsonObject.get(Constants.VIRTUAL_MEM_USED).toString(),
                                jsonObject.get(Constants.VIRTUAL_MEM_TOTAL).toString(), memRemain,
                                resourceInfo.getVirtualMemSize());
                    }
                    resource.setMem(mem);
                }
            }
            edgeResourceInfo.setResource(resource);
            edgeDetails.add(edgeResourceInfo);
        }
        LOGGER.info("edgeDetails: {}", edgeDetails);
        return edgeDetails;
    }

    /**
     * getResourceTemplateInfo.
     */
    public ApmV2Response getResourceTemplateInfo(String accessToken, String tenantId, String packageId,
                                        String appPkgPath) {
        LOGGER.info("inside getResourceTemplateInfo");
        LOGGER.info("apppkgPath: {}", appPkgPath);

        String localFilePath = ApmServiceHelper.getCsarPath(packageId, tenantId, appPkgPath);

        try {
            LOGGER.info("localFilePath: {}", localFilePath);
            //unzip app package
            String intendedDir = apmService.getLocalIntendedDir(packageId, tenantId);
            CompressUtility.unzipApplicationPacakge(localFilePath, intendedDir);

            AppTemplateDto appTemplateDto = apmService.getVduComputeTemplateInfo(tenantId, appPkgPath, packageId);
            appTemplateDto.setAppPackageId(packageId);

            Set<AppTemplateInputAttrDto> sortMap = new TreeSet<>((o1, o2) -> o1.getName().compareTo(o2.getName()));
            sortMap.addAll(appTemplateDto.getInputs());
            appTemplateDto.setInputs(sortMap);

            return new ApmV2Response(appTemplateDto, HttpStatus.OK.value(), "success");
        } catch (ApmException ex) {
            LOGGER.error("failed to resource template from the package", ex.getMessage());
            return new ApmV2Response(null, HttpStatus.BAD_REQUEST.value(), ex.getMessage());
        }
    }

    /**
     * updateResourceTemplateInfo.
     */
    public ApmV2Response updateResourceTemplateInfo(String accessToken, String tenantId, AppTemplateDto appTemplateDto,
                                                   String appPkgPath) {

        try {
            String localFilePath = ApmServiceHelper.getCsarPath(appTemplateDto.getAppPackageId(), tenantId,
                    localDirPath);

            ResourceInfo resourceInfo = apmService.getVduComputeInfo(tenantId, localFilePath,
                    appTemplateDto.getAppPackageId(), appTemplateDto, true);

            Map<String, String> queryKpi = apmSyncHandler.queryKpi(tenantId, accessToken, "openstack");
            List<EdgeResourceInfo> queryKpiList = getResourceUsedInfo(queryKpi, resourceInfo);

            return new ApmV2Response(queryKpiList, HttpStatus.OK.value(), "success");

        } catch (ApmException ex) {
            LOGGER.error("failed to customize resources", ex.getMessage());
            return new ApmV2Response(null, HttpStatus.BAD_REQUEST.value(), ex.getMessage());
        }
    }

    private IntentEnum getIntentType(Intent intent) throws ApmException {
        if (!intent.getMatchAll().isEmpty() && intent.getMatchAny().isEmpty()) {
            return IntentEnum.MATCH_ALL;
        }

        if (!intent.getMatchAny().isEmpty() && intent.getMatchAll().isEmpty()) {
            return IntentEnum.MATCH_ANY;
        }

        if (!intent.getMatchAll().isEmpty() && !intent.getMatchAny().isEmpty()) {
            return IntentEnum.MATCH_BOTH;
        }

        throw new ApmException("Unkown intent");
    }

    /**
     * intent processing.
     *
     * @param accessToken access token
     * @param tenantId    tenantid
     * @param intent      intent
     */
    public List<String> processIntent(String accessToken, String tenantId, Intent intent) throws ApmException {
        LOGGER.info("process intent : {} ", intent);
        String edgeIp = Constants.EMPTY_STRING;
        List<String> edges = new LinkedList<>();

        /*
         * 1. Query all the edges from inventory along with edge properties to compare with the intent input
         * 2. call Query KPI to validate based on cpu and mem usage.
         * */
        try {
            ResponseEntity<String> response = queryAllMecHosts(tenantId, accessToken);
            JsonArray mecHosts = new com.google.gson.JsonParser().parse(response.getBody()).getAsJsonArray();
            Map<String, Map<String, Integer>> edgeKpiProps = queryAllEdgesForKpi(accessToken, tenantId, intent,
                    mecHosts);
            
            Map<String, String> matchAllKpi = new HashMap<>();
            Map<String, String> matchAnyKpi = new HashMap<>();
            Map<String, String> matchAllMatchBothKpi = new HashMap<>();
            Map<String, String> matchAnyMatchBothKpi = new HashMap<>();
            getKpiIntent(intent, matchAllKpi, matchAnyKpi, matchAllMatchBothKpi, matchAnyMatchBothKpi, edgeKpiProps);

            Map<String, Map<String, Object>> edgeProps = getEdgeProperties(tenantId, intent, mecHosts);
            IntentEnum intentMatch = getIntentType(intent);

            for (JsonElement host : mecHosts) {
                LOGGER.info("host is : " + host);

                Map<String, Object> edgePropsMap = edgeProps.get(host.getAsJsonObject().get("mechostIp").getAsString());

                switch (intentMatch) {
                    case MATCH_ALL:
                        edgeIp = processMatchAllIntent(intent, host, edgePropsMap, edgeKpiProps, matchAllKpi);
                        if (!edgeIp.equals(Constants.EMPTY_STRING)) {
                            edges.add(edgeIp);
                        }
                        break;
                    case MATCH_ANY:
                        edgeIp = processMatchAnyIntent(intent, host, edgePropsMap, edgeKpiProps, matchAnyKpi);
                        if (!edgeIp.equals(Constants.EMPTY_STRING)) {
                            edges.add(edgeIp);
                        }
                        break;
                    case MATCH_BOTH:
                        edgeIp = processMatchAllMatchAnyIntent(intent, host, edgePropsMap, edgeKpiProps,
                                matchAllMatchBothKpi, matchAnyMatchBothKpi);
                        if (!edgeIp.equals(Constants.EMPTY_STRING)) {
                            edges.add(edgeIp);
                        }
                        break;
                    default:
                        LOGGER.error("Unknown intent : {}", intentMatch);
                        break;
                }
            }
        } catch (ApmException e) {
            LOGGER.error("exception while processing intent : {}", e.getMessage());
            throw e;
        }
        if (edges.isEmpty()) {
            throw new ApmException("No edge found matching intent");
        }
        return edges;
    }

    private String processMatchAllIntent(Intent intent, JsonElement host, Map<String, Object> edgePropsMap,
                                         Map<String, Map<String, Integer>> edgeKpiProps,
                                         Map<String, String> matchAllKpi) {
        String edgeIp = Constants.EMPTY_STRING;

        LOGGER.info("process match all intent");
        LOGGER.info("Match all intent: {}", intent.getMatchAll().entrySet());
        LOGGER.info("Edge properties: {}", edgePropsMap.entrySet());

        if (intent.getMatchAll().isEmpty() || edgePropsMap.entrySet().containsAll(intent.getMatchAll().entrySet())) {
            //matchAll condition
            if (!matchAllKpi.isEmpty()) {
                Map<String, Integer> edgeKpiParams = edgeKpiProps.get(host.getAsJsonObject()
                        .get("mechostIp").getAsString());
                if (isAllKpiMatch(edgeKpiParams, matchAllKpi)) {
                    edgeIp = host.getAsJsonObject().get("mechostIp").getAsString();
                    LOGGER.info("edgeIp in matchAll set: {}", edgeIp);
                    return edgeIp;
                }
            } else if (intent.getMatchAll().size() > 0) {
                edgeIp = host.getAsJsonObject().get("mechostIp").getAsString();
                LOGGER.info("edgeIp in matchAll set: {}", edgeIp);
                return edgeIp;
            }
        }
        return edgeIp;
    }

    private String processMatchAnyIntent(Intent intent, JsonElement host, Map<String, Object> edgePropsMap,
                                         Map<String, Map<String, Integer>> edgeKpiProps,
                                         Map<String, String> matchAnyKpi) {
        String edgeIp = Constants.EMPTY_STRING;

        LOGGER.info("process match any intent ");
        LOGGER.info("Match any intent : {}", intent.getMatchAny().entrySet());
        LOGGER.info("Edge properties: {}", edgePropsMap.entrySet());

        if (intent.getMatchAny().isEmpty() || !Collections.disjoint(edgePropsMap.entrySet(),
                intent.getMatchAny().entrySet())) {

            if (!matchAnyKpi.isEmpty()) {
                Map<String, Integer> edgeKpiParams = edgeKpiProps.get(host.getAsJsonObject()
                        .get("mechostIp").getAsString());
                if (isAnyKpiMatch(edgeKpiParams, matchAnyKpi)) {
                    //matchAny condition
                    edgeIp = host.getAsJsonObject().get("mechostIp").getAsString();
                    LOGGER.info("edgeIp in matchAny set: " + edgeIp);
                    return edgeIp;
                }
            } else if (intent.getMatchAny().size() > 0) {
                edgeIp = host.getAsJsonObject().get("mechostIp").getAsString();
                LOGGER.info("edgeIp in matchAny set: " + edgeIp);
                return edgeIp;
            }
        }

        return edgeIp;
    }

    private String processMatchAllMatchAnyIntent(Intent intent, JsonElement host, Map<String, Object> edgePropsMap,
                                                 Map<String, Map<String, Integer>> edgeKpiProps,
                                                 Map<String, String> matchAllMatchBothKpi,
                                                 Map<String, String> matchAnyMatchBothKpi) {

        String edgeIp = Constants.EMPTY_STRING;

        LOGGER.info("process match all and match any intent ");
        LOGGER.info("Match all intent : {}", intent.getMatchAll().entrySet());
        LOGGER.info("Match any intent : {}", intent.getMatchAny().entrySet());
        LOGGER.info("Edge properties: {}", edgePropsMap.entrySet());

        //both matchall and matchany condition
        if (intent.getMatchAll().isEmpty() || edgePropsMap.entrySet().containsAll(intent.getMatchAll().entrySet())) {
            //matchAll condition
            if (!matchAllMatchBothKpi.isEmpty()) {
                Map<String, Integer> edgeKpiParams = edgeKpiProps.get(host.getAsJsonObject()
                        .get("mechostIp").getAsString());
                if (!isAllKpiMatch(edgeKpiParams, matchAllMatchBothKpi)) {
                    return Constants.EMPTY_STRING;
                }
            }
            if (intent.getMatchAny().isEmpty() || !Collections.disjoint(edgePropsMap.entrySet(),
                    intent.getMatchAny().entrySet())) {
                //matchAny condition

                if (!matchAnyMatchBothKpi.isEmpty()) {
                    Map<String, Integer> edgeKpiParams = edgeKpiProps.get(host.getAsJsonObject()
                            .get("mechostIp").getAsString());
                    if (isAnyKpiMatch(edgeKpiParams, matchAnyMatchBothKpi)) {
                        edgeIp = host.getAsJsonObject().get("mechostIp").getAsString();
                        LOGGER.info("edgeIp in matchAll and matchAny set: " + edgeIp);
                        return edgeIp;
                    }
                }
                if (intent.getMatchAny().size() > 0) {
                    edgeIp = host.getAsJsonObject().get("mechostIp").getAsString();
                    LOGGER.info("edgeIp in matchAll and matchAny set: " + edgeIp);
                    return edgeIp;
                }
            }
        }
        return edgeIp;
    }

    private ResponseEntity<String> queryAllMecHosts(String tenantId, String accessToken) {
        String url = new StringBuilder(inventoryService).append(":").append(inventoryServicePort)
                .append("/inventory/v1").append("/tenants/")
                .append(tenantId).append("/mechosts").toString();
        HttpHeaders header = new HttpHeaders();
        header.setContentType(MediaType.APPLICATION_JSON);

        return restService.sendRequest(url, HttpMethod.GET, accessToken, null);
    }

    private void getKpiIntent(Intent intent, Map<String, String> matchAllKpi, Map<String, String> matchAnyKpi,
                              Map<String, String> matchAllMatchBothKpi, Map<String, String> matchAnyMatchBothKpi,
                              Map<String, Map<String, Integer>> edgeKpiProps) {

        if (!intent.getMatchAll().isEmpty() && intent.getMatchAny().isEmpty()) {
            if (!edgeKpiProps.isEmpty()) {
                if (intent.getMatchAll().get(Constants.CPU_USAGE) != null) {
                    matchAllKpi.put(Constants.CPU_USAGE, intent.getMatchAll().get(Constants.CPU_USAGE));
                    intent.getMatchAll().remove(Constants.CPU_USAGE);
                }
                if (intent.getMatchAll().get(Constants.MEM_USAGE) != null) {
                    matchAllKpi.put(Constants.MEM_USAGE, intent.getMatchAll().get(Constants.MEM_USAGE));
                    intent.getMatchAll().remove(Constants.MEM_USAGE);
                }
            }
        }

        if (!intent.getMatchAny().isEmpty() && intent.getMatchAll().isEmpty()) {

            if (!edgeKpiProps.isEmpty()) {
                if (intent.getMatchAny().get(Constants.CPU_USAGE) != null) {
                    matchAnyKpi.put(Constants.CPU_USAGE, intent.getMatchAny().get(Constants.CPU_USAGE));
                    intent.getMatchAny().remove(Constants.CPU_USAGE);
                }
                if (intent.getMatchAny().get(Constants.MEM_USAGE) != null) {
                    matchAnyKpi.put(Constants.MEM_USAGE, intent.getMatchAny().get(Constants.MEM_USAGE));
                    intent.getMatchAny().remove(Constants.MEM_USAGE);
                }
            }
        }

        if (!intent.getMatchAll().isEmpty() && !intent.getMatchAny().isEmpty()) {

            if (!edgeKpiProps.isEmpty()) {

                if (intent.getMatchAll().get(Constants.CPU_USAGE) != null) {
                    matchAllMatchBothKpi.put(Constants.CPU_USAGE, intent.getMatchAll().get(Constants.CPU_USAGE));
                    intent.getMatchAll().remove(Constants.CPU_USAGE);
                }
                if (intent.getMatchAll().get(Constants.MEM_USAGE) != null) {
                    matchAllMatchBothKpi.put(Constants.MEM_USAGE, intent.getMatchAll().get(Constants.MEM_USAGE));
                    intent.getMatchAll().remove(Constants.MEM_USAGE);
                }
                if (intent.getMatchAny().get(Constants.CPU_USAGE) != null) {
                    matchAnyMatchBothKpi.put(Constants.CPU_USAGE, intent.getMatchAny().get(Constants.CPU_USAGE));
                    intent.getMatchAny().remove(Constants.CPU_USAGE);
                }
                if (intent.getMatchAny().get(Constants.MEM_USAGE) != null) {
                    matchAnyMatchBothKpi.put(Constants.MEM_USAGE, intent.getMatchAny().get(Constants.MEM_USAGE));
                    intent.getMatchAny().remove(Constants.MEM_USAGE);
                }
            }
        }
    }

    private boolean isAnyKpiMatch(Map<String, Integer> edgeKpiParams, Map<String, String> matchAnyKpiIntent) {

        int matchCnt = 0;

        for (Map.Entry<String, String> entry : matchAnyKpiIntent.entrySet()) {
            if (entry.getKey().equals(Constants.CPU_USAGE)) {
                String cpuUsageIntent = entry.getValue();
                LOGGER.info("values cpuUsageIntent: {}", cpuUsageIntent);

                if (cpuUsageIntent.contains(">")) {
                    Integer cpuUsageIntentVal = Integer.parseInt(cpuUsageIntent
                            .replaceAll("[\\D]", Constants.EMPTY_STRING));
                    if (edgeKpiParams.get(Constants.CPU_USAGE) > cpuUsageIntentVal) {
                        LOGGER.info("cpuusage {}", edgeKpiParams.get(Constants.CPU_USAGE));
                        matchCnt++;
                    }
                }
                if (cpuUsageIntent.contains("<")) {
                    Integer cpuUsageIntentVal = Integer.parseInt(cpuUsageIntent
                            .replaceAll("[\\D]", Constants.EMPTY_STRING));
                    if (edgeKpiParams.get(Constants.CPU_USAGE) < cpuUsageIntentVal) {
                        LOGGER.info("cpuusage {}", edgeKpiParams.get(Constants.CPU_USAGE));
                        matchCnt++;
                    }
                }
            }

            if (entry.getKey().equals(Constants.MEM_USAGE)) {
                String memUsageIntent = entry.getValue();
                LOGGER.info("values memUsageIntent: {}", memUsageIntent);

                if (memUsageIntent.contains(">")) {
                    Integer memUsageIntentVal = Integer.parseInt(memUsageIntent
                            .replaceAll("[\\D]", Constants.EMPTY_STRING));
                    if (edgeKpiParams.get(Constants.MEM_USAGE) > memUsageIntentVal) {
                        LOGGER.info("memusage {}", edgeKpiParams.get(Constants.MEM_USAGE));
                        matchCnt++;
                    }
                }
                if (memUsageIntent.contains("<")) {
                    Integer memUsageIntentVal = Integer.parseInt(memUsageIntent
                            .replaceAll("[\\D]", Constants.EMPTY_STRING));
                    if (edgeKpiParams.get(Constants.MEM_USAGE) < memUsageIntentVal) {
                        LOGGER.info("memusage {}", edgeKpiParams.get(Constants.MEM_USAGE));
                        matchCnt++;
                    }
                }
            }
        }

        return matchCnt > 0;
    }

    private boolean isAllKpiMatch(Map<String, Integer> edgeKpiParams, Map<String, String> matchAllKpiIntent) {

        int matchCnt = 0;

        for (Map.Entry<String, String> entry : matchAllKpiIntent.entrySet()) {
            if (entry.getKey().equals(Constants.CPU_USAGE)) {
                String cpuUsageIntent = entry.getValue();
                LOGGER.info("values cpuUsageIntent: {}", cpuUsageIntent);

                if (cpuUsageIntent.contains(">")) {
                    Integer cpuUsageIntentVal = Integer.parseInt(cpuUsageIntent
                            .replaceAll("[\\D]", Constants.EMPTY_STRING));
                    if (edgeKpiParams.get(Constants.CPU_USAGE) > cpuUsageIntentVal) {
                        LOGGER.info("OK cpuusage {}", edgeKpiParams.get(Constants.CPU_USAGE));
                        matchCnt++;
                    }
                }
                if (cpuUsageIntent.contains("<")) {
                    Integer cpuUsageIntentVal = Integer.parseInt(cpuUsageIntent
                            .replaceAll("[\\D]", Constants.EMPTY_STRING));
                    if (edgeKpiParams.get(Constants.CPU_USAGE) < cpuUsageIntentVal) {
                        LOGGER.info("OK cpuusage {}", edgeKpiParams.get(Constants.CPU_USAGE));
                        matchCnt++;
                    }
                }
            }

            if (entry.getKey().equals(Constants.MEM_USAGE)) {
                String memUsageIntent = entry.getValue();
                LOGGER.info("values memUsageIntent: {}", memUsageIntent);

                if (memUsageIntent.contains(">")) {
                    Integer memUsageIntentVal = Integer.parseInt(memUsageIntent
                            .replaceAll("[\\D]", Constants.EMPTY_STRING));
                    if (edgeKpiParams.get(Constants.MEM_USAGE) > memUsageIntentVal) {
                        LOGGER.info("OK memusage {}", edgeKpiParams.get(Constants.MEM_USAGE));
                        matchCnt++;
                    }
                }
                if (memUsageIntent.contains("<")) {
                    Integer memUsageIntentVal = Integer.parseInt(memUsageIntent
                            .replaceAll("[\\D]", Constants.EMPTY_STRING));
                    if (edgeKpiParams.get(Constants.MEM_USAGE) < memUsageIntentVal) {
                        LOGGER.info("OK memusage {}", edgeKpiParams.get(Constants.MEM_USAGE));
                        matchCnt++;
                    }
                }
            }
        }

        return matchAllKpiIntent.size() == matchCnt;
    }

    private Map<String, Map<String, Integer>> getEdgesKpi(String accessToken, String tenantId, JsonArray mecHosts) {

        Map<String, Map<String, Integer>> edgesKpi = new HashMap<>();
        for (JsonElement host : mecHosts) {
            try {
                String url1 = new StringBuilder("mecm-appo").append(":")
                        .append(8091).append("/appo/v1").append("/tenants/").append(tenantId)
                        .append("/hosts/").append(host.getAsJsonObject().get("mechostIp").getAsString())
                        .append("/kpi").toString();

                ResponseEntity<String> response1 = restService.sendRequest(url1, HttpMethod.GET, accessToken, null);
                LOGGER.info("Query kpi response: {}", response1);
                JsonObject json = new com.google.gson.JsonParser().parse(response1.getBody()).getAsJsonObject();
                JsonObject output = json.get("data").getAsJsonObject();

                Map<String, Integer> kpiParams = new HashMap<>();

                //K8s
                if (output.get(Constants.CPU_USAGE) != null) {
                    JsonObject j = output.get(Constants.CPU_USAGE).getAsJsonObject();
                    double total = j.get("total").getAsDouble();
                    double used = j.get("used").getAsDouble();
                    kpiParams.put(Constants.CPU_USAGE, (int) ((used / total) * 100));

                    j = output.get(Constants.MEM_USAGE).getAsJsonObject();
                    total = j.get("total").getAsDouble();
                    used = j.get("used").getAsDouble();
                    kpiParams.put(Constants.MEM_USAGE, (int) ((used / total) * 100));
                } else if (output.get(Constants.VIRTUAL_CPU_USED) != null) {
                    //VM
                    double total = output.get(Constants.VIRTUAL_CPU_TOTAL).getAsDouble();
                    double used = output.get(Constants.VIRTUAL_CPU_USED).getAsDouble();
                    kpiParams.put(Constants.CPU_USAGE, (int) ((used / total) * 100));

                    total = output.get(Constants.VIRTUAL_MEM_TOTAL).getAsDouble();
                    used = output.get(Constants.VIRTUAL_MEM_USED).getAsDouble();
                    kpiParams.put(Constants.MEM_USAGE, (int) ((used / total) * 100));
                }
                edgesKpi.put(host.getAsJsonObject().get("mechostIp").getAsString(), kpiParams);

            } catch (ApmException | NoSuchElementException ex) {
                LOGGER.info("Failed to get KPI {} ", ex.getMessage());
            }
        }
        LOGGER.info("Edges KPIs : {}", edgesKpi);
        return edgesKpi;
    }

    private Map<String, Map<String, Object>> getEdgeProperties(String tenantId, Intent intent,
                                                               JsonArray mecHosts) throws ApmException {
        LOGGER.info("tenant id is: {} and intent is: {} ", tenantId, intent);

        Map<String, Map<String, Object>> edges = new HashMap<>();

        try {
            for (JsonElement host : mecHosts) {
                String edgeKey = host.getAsJsonObject().get("mechostIp").getAsString();
                Map<String, Object> mecHost = new Gson().fromJson(host,
                        new TypeToken<HashMap<String, Object>>() {
                        }.getType());
                LOGGER.info("mecHost : {}", mecHost);
                edges.put(edgeKey, mecHost);
            }
        } catch (Exception e) {
            LOGGER.error("exception is : {}", e.getMessage());
            throw new ApmException("combine edge properties failed: " + e.getMessage());
        }
        LOGGER.info("Edges properties : {}", edges);
        return edges;
    }

    private Map<String, Map<String, Integer>> queryAllEdgesForKpi(String accessToken, String tenantId, Intent intent,
                                                                  JsonArray mecHosts) throws ApmException {
        LOGGER.info("tenant id is: {} and intent is: {} ", tenantId, intent);

        Map<String, Map<String, Integer>> edgesKpi = new HashMap<>();
        try {

            if (intent.getMatchAll().containsKey(Constants.CPU_USAGE)
                    || intent.getMatchAll().containsKey(Constants.MEM_USAGE)
                    || intent.getMatchAny().containsKey(Constants.CPU_USAGE)
                    || intent.getMatchAny().containsKey(Constants.MEM_USAGE)) {

                edgesKpi = getEdgesKpi(accessToken, tenantId, mecHosts);
            }
        } catch (Exception e) {
            LOGGER.error("exception is : {}", e.getMessage());
            throw new ApmException("Query edge KPI properties failed: " + e.getMessage());
        }
        LOGGER.info("Edges KPI properties : {}", edgesKpi);
        return edgesKpi;
    }
}
